/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	Model.cpp
*
*	Comments	-	See Model.h
*
**************************************************************************************/
#include "../Include/Model.h"
#include "../Include/XMLParser.h"
#include "../Include/GraphicsResourceManager.h"
#include "../Include/Engine.h"
#include "../Include/GlobalGraphicsStrings.h"
#include "../Include/IParameter.h"

namespace Pulse
{
	ModelDesc::ModelDesc( void )
		: m_pMaterialToSet( PSX_NULL ), m_pGeometryToSet( PSX_NULL )
	{
		
	}

	ModelDesc::~ModelDesc( void )
	{
		ClearData();
	}

	void ModelDesc::SetToLoad( const CHAR *pFilename ) 
	{
		ClearData();
		m_filename = pFilename; 
	}

	void ModelDesc::SetManual( Material *pMaterialToSet, Geometry *pGeometryToSet )
	{
		ClearData();

		m_pMaterialToSet = pMaterialToSet;

		if ( pMaterialToSet )
			pMaterialToSet->AddRef();

		m_pGeometryToSet = pGeometryToSet;

		if ( pGeometryToSet )
			pGeometryToSet->AddRef();
	}

	void ModelDesc::ClearData( void )
	{
		m_filename.Clear();
		PSX_SafeRelease( m_pMaterialToSet );
		PSX_SafeRelease( m_pGeometryToSet );
	}

	ModelDesc & ModelDesc::operator = ( ModelDesc &rhs )
	{
		if ( this == &rhs )
			return *this;

		ClearData();

		m_filename = rhs.m_filename;
		m_pMaterialToSet = rhs.m_pMaterialToSet;
		m_pGeometryToSet = rhs.m_pGeometryToSet;

		if ( m_pMaterialToSet )
			m_pMaterialToSet->AddRef();

		if ( m_pGeometryToSet )
			m_pGeometryToSet->AddRef();
	
		return *this;
	}

	Model::Model( void )
	{

	}

	Model::~Model( void )
	{

	}

	BOOL Model::CreateResource( BaseResDesc *pDesc )
	{
		if ( pDesc->GetType() != GetType() )
			return FALSE;

		m_desc = *((ModelDesc*)pDesc);

		if ( PerformInitialize( m_desc.IsAsyncLoad() ) == EErrorCode::OKAY )
			SetFlagCreated();

		return IsCreated();
	}

	BOOL Model::DestroyResource( void )
	{
		UnloadResource();
		SetFlagDestroyed();	
		return TRUE;	
	}

	BOOL Model::LoadResource( void )
	{
		if ( Initialize() == EErrorCode::OKAY )
		{
			SetFlagLoaded();	
			return TRUE;	
		}

		return FALSE;
	}

	BOOL Model::UnloadResource( void )
	{
		Cleanup();
		SetFlagUnloaded();
		return TRUE;	
	}

	EErrorCode::Type Model::Initialize( void )
	{
		// Load or manual creation?

		if ( m_desc.m_filename.GetLength() )
		{
			// Load model

			String fullPath = PSX_PATH_MODELS + m_desc.m_filename;
			XMLDocumentPtr pDoc = XMLParser::Load( fullPath.GetBuffer() );

			if ( pDoc.IsNull() )
			{
				PSX_PushError( "Failed to load Model file." );
				return EErrorCode::_ERROR;
			}

			XMLElementPtr pElem = pDoc->FindChild( PSX_String("Model") );

			if ( pElem.IsNull() )
			{
				PSX_PushError( "No Model info." );
				return EErrorCode::INVALID_FORMAT;
			}

			EErrorCode::Type retCode;

			retCode = ProcessModel( pElem );

			if ( retCode != EErrorCode::OKAY )
				return EErrorCode::_ERROR;
		}
		else
		{
			// Manual set

			// Desc must have material and geometry set.
			if ( m_desc.m_pMaterialToSet == PSX_NULL || m_desc.m_pGeometryToSet == PSX_NULL )
			{
				PSX_PushError( "Manual setting of model doesn't have material and or geometry set." );
				return EErrorCode::INVALID_PARAMETER;
			}

			m_desc.m_pGeometryToSet->AddRef();
			m_desc.m_pMaterialToSet->AddRef();

			m_pGeometry = m_desc.m_pGeometryToSet;
			m_pMaterial = m_desc.m_pMaterialToSet;

		}

		return EErrorCode::OKAY;
	}

	void Model::Cleanup( void )
	{
		if ( m_pGeometry )
			m_pGeometry->Release();

		if ( m_pMaterial )
			m_pMaterial->Release();
	}

	IParameterBinder * Model::AddParameter( IParameter *pParam )
	{
		return m_params.AddParameterBinder( pParam );
	}

	BOOL Model::RemoveParameter( const CHAR *pParamName )
	{
		return m_params.RemoveParameterBinder( pParamName );
	}

	void Model::ClearParameters( void )
	{
		m_params.ClearParameters();
	}

	Material * Model::GetMaterial( void )
	{
		m_pMaterial->AddRef();
		return m_pMaterial;
	}

	Geometry * Model::GetGeometry( void )
	{
		m_pGeometry->AddRef();
		return m_pGeometry;
	}

	EErrorCode::Type Model::ProcessModel( XMLElementPtr pElem )
	{
		// Must have Geometry material and effect.

		EErrorCode::Type retCode;
		XMLElementPtr pChild = pElem->FindChild( PSX_String("Parameters") );

		// Parameters are optional
		if ( pChild.IsValid() )
		{
			retCode = m_params.ParseParameters( pChild );
		
			if ( retCode != EErrorCode::OKAY )
				return EErrorCode::_ERROR;
		}
		
		pChild = pElem->FindChild( PSX_String("Geometry") );
		
		if ( pChild.IsNull() )
		{
			PSX_PushError( "No geometry info." );
			return EErrorCode::INVALID_FORMAT;
		}

		retCode = ProcessGeometry( pChild );

		if ( retCode != EErrorCode::OKAY )
		{
			return EErrorCode::_ERROR;
		}

		pChild = pElem->FindChild( PSX_String("Material") );

		if ( pChild.IsNull() )
		{
			PSX_PushError( "No Material info." );
			return EErrorCode::INVALID_FORMAT;
		}
		
		retCode = ProcessMaterial( pChild );

		if ( retCode != EErrorCode::OKAY )
		{
			return EErrorCode::_ERROR;
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type Model::ProcessGeometry( XMLElementPtr pElem )
	{
		XMLElementPtr pChild = pElem->FindChild( PSX_String("File") );
		
		if ( pChild.IsNull() )
		{
			PSX_PushError( "No file info in geometry info." );
			return EErrorCode::INVALID_FORMAT;
		}

		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		String filename = pChild->GetValue();
		GeometryDesc desc;
		
		desc.SetToLoad( filename.GetBuffer() );
		m_pGeometry = pGM->CreateGeometry( filename.GetBuffer(), &desc );

		if ( m_pGeometry == PSX_NULL )
		{
			PSX_PushError( "Failed to create geometry." );
			return EErrorCode::_ERROR;
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type Model::ProcessMaterial( XMLElementPtr pElem )
	{
		XMLElementPtr pChild = pElem->FindChild( PSX_String("File") );
		
		if ( pChild.IsNull() )
		{
			PSX_PushError( "No file info in geometry info." );
			return EErrorCode::INVALID_FORMAT;
		}

		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		String filename = pChild->GetValue();
		MaterialDesc desc;
		
		desc.SetToLoad( filename.GetBuffer() );
		//m_pMaterial = pGM->CreateMaterial( filename.GetBuffer(), &desc );
		// Materials are created unique so that we can change some parameters
		//	without affectig others.
		m_pMaterial = pGM->CreateMaterial( &desc );

		if ( m_pMaterial == PSX_NULL )
		{
			PSX_PushError( "Failed to create geometry." );
			return EErrorCode::_ERROR;
		}

		// Fill unsupplied parameters...
		Effect *pEffect = m_pMaterial->GetEffect();
		SIZE_T numMaterialRequirements = pEffect->GetNumMaterialRequirements();
		MaterialElementRequirement *pMatRequirements = pEffect->GetMaterialRequirements();
		
		for ( SIZE_T i = 0; i < numMaterialRequirements; ++i )
		{
			MaterialElementRequirement *pMatElem = &pMatRequirements[i];

			// Some material requirements needs to be passed off to a separate 
			//	render stage/pass/phase. (i.e. environment map, shadow map )
			if ( m_pMaterial->IsMaterialElementAvailable( pMatElem->m_type, pMatElem->m_name.GetBuffer() ) == FALSE )
			{
				// If the material requirement is user defined see if we can just create a blank one.
				if ( pMatElem->m_source == EMaterialSource::USER )
				{
					// Just create an empty one so that it is set to default.
					m_pMaterial->GetMaterialParameters()->AddParameterBinder( pMatElem->m_type, pMatElem->m_name.GetBuffer() );
				}
				else
				{
					// Figure out if this can be supplied by a separate render stage.
					PSX_PushError( "Not yet implemented!" );
				}
			}
		}

		return EErrorCode::OKAY;
	}

	BOOL Model::VerifyRenderRequirements( void ) const
	{
		// We verify the effect's requirements against our supplied geometry
		//	and material.
		// TODO: We forgot to take into account that models will be encapsulated in a (game) object instance.
		//	which in turn the (game) object instance can also supply additional parameters for rendering.
		//	We must figure out a way to fix this.
		if ( m_pGeometry == PSX_NULL )
		{
			PSX_PushError( "No geometry attached to this model." );
			return FALSE;
		}

		if ( m_pMaterial == PSX_NULL )
		{
			PSX_PushError( "No material attached to this model." );
			return FALSE;
		}

		Effect *pEffect = m_pMaterial->GetEffect();
		SIZE_T numGeometryRequirements = pEffect->GetNumGeometryRequirements();
		SIZE_T numMaterialRequirements = pEffect->GetNumMaterialRequirements();
		GeometryElementRequirement *pGeoRequirements = pEffect->GetGeometryRequirements();
		MaterialElementRequirement *pMatRequirements = pEffect->GetMaterialRequirements();

		// Don't need this effect anymore.
		PSX_SafeRelease( pEffect );

		// Verify geometry requirement
		for ( SIZE_T i = 0; i < numGeometryRequirements; ++i )
		{
			GeometryElementRequirement *pGeoElem = &pGeoRequirements[i];

			if ( m_pGeometry->IsVertexElementAvailable( pGeoElem->m_type )  == FALSE )
				return FALSE;
		}

		// Verify material requirement
		for ( SIZE_T i = 0; i < numMaterialRequirements; ++i )
		{
			MaterialElementRequirement *pMatElem = &pMatRequirements[i];

			// Some material requirements needs to be passed off to a separate 
			//	render stage/pass/phase. (i.e. environment map, shadow map )
			if ( m_pMaterial->IsMaterialElementAvailable( pMatElem->m_type, pMatElem->m_name.GetBuffer() ) == FALSE )
			{
				// Figure out if this can be supplied by a separate render stage.
				if ( pMatElem->m_source == EMaterialSource::SYSTEM )
				{
					// TODO: Figure out how would the render path know to create special render entries...
					if ( pMatElem->m_type == EParameter::TEXTURE2D )
					{
						// Planar reflection requires a reflection texture
						if ( pMatElem->m_name == GFX_PARAM_TEXTURE_PLANAR_REFLECTION )
						{
							// Moved in the renderpath
							continue;

							//const SIZE_T textureReflectionSize = 512; // TODO: Set this in graphics/engine configuration script.
							//GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
							//Texture2DDesc desc;
							//Texture2D *pTexture;

							//desc.SetAsRenderTarget( textureReflectionSize, textureReflectionSize );
							//pTexture = pGM->CreateTexture2D( &desc );

							//EErrorCode::Type retCode = m_pMaterial->AddParameter( GFX_PARAM_TEXTURE_PLANAR_REFLECTION, pTexture );

							//if ( retCode != EErrorCode::OKAY )
							//{
							//	PSX_PushError( "Failed to create reflection texture." );
							//	return FALSE;
							//}
						}
						else
						{
							PSX_PushError( "Not yet implemented!" );
							return FALSE;
						}

					}
				}
				else
				{
					PSX_PushError( "Not yet implemented!" );
					return FALSE;
				}
			}
		}

		return TRUE;
	}
}